'''
$LastChangedBy: csgillespie $
$LastChangedDate: 2007-06-06 11:08:06 +0000 (Wed, 06 Jun 2007) $
Colin Gillespie (c.gillespie@ncl.ac.uk)
Imported into the BASISTools module
As the name suggest, this module is used to modify SBML
'''

class ModifySBML:
	'''Used to modify elements to the SBML model. Called in BASISTools.
	
	self.m is the libSBML getModel element
	self.sbml_parts is a tuple of ('Species','Events')
	self.alter_dict is the dictionary of thing to be altered, e.g.
	{Species:{'ATP':{'Id':'new_atp'}}}
	'''
	
	def __init__(self, model, sbml_parts, alter_dict, recursive=1):
		self.m = model
		self.sbml_parts = sbml_parts
		self.alter_dict = alter_dict
		self.recursive = recursive
		
	def alterModel(self):
		'''After the class has been created, we could modify
		each element individually, e.g.
		m.modifyCompartments()
		m.modifySpecies()
		etc,
		
		This method does all that for you.
		'''
		for part in self.sbml_parts:
			getattr(self, 'modify' + part)()
		return 1

	def modifyCompartments(self):
		'''Main method for modify comparments'''
		for co in self.m.getListOfCompartments():
			if co.getId() in self.alter_dict['Co'].keys():
				self.__modifyCompartment(co)

	def __modifyCompartment(self, co):
		'''Determine if outside has been changed'''
		for key, value in self.alter_dict['Co'][co.getId()].items():
			if self.recursive and key in ['Outside']:
				self.__setOutside(co, value)
			else:
				getattr(co, 'set'+key)(value)
		return 1
	
	def __setOutside(self, co, outside):
		'''Set new outside'''
		#Has outside been changed
		if outside:
			co.setOutside(outside)
			return 1
		
		#Was there an old outside
		old_outside = co.getOutside()
		if not old_outside: 
			co.unsetOutside()
			return 1
		
		#Has the old outside name been changed?
		old_outside = self.alter_dict['Co'][old_outside].get('Id', old_outside)
		co.setOutside(old_outside)
		return 1
	
	def modifySpecies(self):
		'''Main method for modifying Species'''
		for sp in self.m.getListOfSpecies():
			sp_dict = self.alter_dict['Sp'].get(sp.getId(), {})
			self.__modifySpeciesCompartment(sp)
			for key, value in sp_dict.items():
				getattr(sp, 'set'+key)(value)
		return 1
	
	def __modifySpeciesCompartment(self, sp):
		'''Checks to see if the species compartment needs to be modified'''
		new_compartment = self.alter_dict['Co'].get(sp.getCompartment(), None)
		if self.recursive and new_compartment and new_compartment.get('Id', None):
			sp.setCompartment(new_compartment.get('Id'))
		return 1
	
	def modifyReactions(self):
		'''Main method for modifying Reactions'''
		for rea in self.m.getListOfReactions():
			if self.alter_dict['Pa'] or self.alter_dict['Sp'] or \
				rea.getId() in self.alter_dict['Re'].keys():
				self.__modifyReaction(rea)
		return 1

	def __modifyReaction(self, rea):
		'''Takes care of the reaction id, reversible, fast'''
		if self.recursive:
			self.__setSpecies(rea)
		re_dict = self.alter_dict['Re'].get(rea.getId(), {}).items()
		for key, value in re_dict:
			if key in ['Id', 'Reversible', 'Fast']:
				getattr(rea, 'set'+key)(value)
			elif key == 'KineticLaw':
				self.__setKineticLaw(rea, value)
		return 1
	
	def __setKineticLaw(self, rea, value):
		'''Value should be of the form 
		{'Formula':'XXX', 'listOfParameters':[{'old_id':{'Id':'new_id', 'Value':'new_value'}}]}
		'''
		if value.get('Formula'):
			rea.getKineticLaw().setFormula(value.get('Formula'))
		
		#XXX: This isn't very efficient, since we loop through all the local parameters
		# but who cares?
		if value.get('listOfParameters'):
			self.__modifyLocalParameters(value.get('listOfParameters'), rea)
	
	def __modifyLocalParameters(self, list_of_pa, rea):
		'''Loops throught local parameters and modifies them'''
		#Change the local parameter list in a dictionary 
		#{'old_id':{'Id','fdsf', 'Value':'10'}, 'old_id2':{...}
		dict_of_pa = {}
		for item in list_of_pa:
			assert len(item.keys()) == 1
			old_id = item.keys()[0]
			dict_of_pa[old_id] = item[old_id]
		
		#Keep a list of local parameters for change KL
		list_of_local_parameters = []
		for pa in rea.getKineticLaw().getListOfParameters():
				#Do we need to change this parameter?
				if pa.getId() in dict_of_pa.keys():
					old_id = pa.getId()
					new_id = dict_of_pa[pa.getId()].get('Id')
					#Set the new id
					if new_id:
						self.alter_dict['Local_Pa']={old_id:{'Id':new_id}}
						pa.setId(new_id)
					#Set the new value
					new_value = dict_of_pa[old_id].get('Value')
					if new_value:
						pa.setValue(new_value)
				else:
					list_of_local_parameters.append(pa.getId())
		#Change the Kinetic Law
		self.__changeKineticLaw(rea.getKineticLaw().getMath(), list_of_local_parameters)
		return True
		
	
	def __setSpecies(self, rea):
		'''Checked for renaming of species'''
		types = ['Reactants', 'Products', 'Modifiers']
		check_kinetic_law = 0
		for typ in types:
			for sp in getattr(rea, 'getListOf' + typ)():
				if self.__hasSpBeenRenamed(sp):
					check_kinetic_law  = self.__renameSpecies(sp)
				elif self.alter_dict['Pa']:
					check_kinetic_law = 1
		
		#Has the kinetic law actually been set
		if check_kinetic_law and rea.isSetKineticLaw():
			kinetic_law = rea.getKineticLaw()
			if kinetic_law.isSetFormula():
				local_par_list = [pa.getId() for pa in kinetic_law.getListOfParameters()]
				if self.__changeKineticLaw(kinetic_law.getMath(), local_par_list) is 0:
					return False
		return True
	
	def __hasSpBeenRenamed(self, sp):
		'''Has species been renamed'''
		sp_dict = self.alter_dict['Sp'].get(sp.getSpecies(), {})
		if sp_dict.get('Id', None):
			return True
		return False
	
	def __renameSpecies(self, sp):
		'''Rename species'''
		new_species_id = self.alter_dict['Sp'][sp.getSpecies()].get('Id')
		sp.setSpecies(new_species_id)
		return 1
	
	def __changeKineticLaw(self, tree, local_par_list):
		'''Change the Kinetic Law'''
		if not tree: 
			return
		if tree.isName():
			self.__changeName(tree, local_par_list)
		self.__changeKineticLaw(tree.getLeftChild(), local_par_list)
		self.__changeKineticLaw(tree.getRightChild(), local_par_list)
	
	def __changeName(self, tree, local_par_list):
		'''Change the name in Kinetic Law'''
		if tree.getName() in local_par_list: 
			return 1
		
		if tree.getName() in self.alter_dict['Sp'].keys():
			new_sp_id = self.alter_dict['Sp'][tree.getName()].get('Id', None)
			if new_sp_id:
				tree.setName(new_sp_id)
			return 1
		
		if tree.getName() in self.alter_dict['Pa'].keys():
			new_pa_id = self.alter_dict['Pa'][tree.getName()].get('Id')
			tree.setName(new_pa_id)
			return 1
		
		if tree.getName() in self.alter_dict.get('Local_Pa', {}).keys():
			new_pa_id = self.alter_dict['Local_Pa'][tree.getName()].get('Id')
			tree.setName(new_pa_id)
			return 1
	
	def modifyParameters(self):
		'''Main method for modifying Parameters'''
		for pa in self.m.getListOfParameters():
			if pa.getId() in self.alter_dict['Pa'].keys():
				pa_dict = self.alter_dict['Pa'].get(pa.getId(), {})
				for key, value in pa_dict.items():
					getattr(pa, 'set'+key)(value)
		return 1		

	def modifyEvents(self):
		'''Main method for modifying Events'''
		for ev in self.m.getListOfEvents():
			if self.recursive:
				pass
			elif self.alter_dict['Sp'] or self.alter_dict['Pa']:
				if ev.isSetTrigger():#Check that there is a trigger
					self.__changeMathInEvent(ev.getTrigger())
			
			ev_dict = self.alter_dict['Ev'].get(ev.getId(), {}).items()
			for key, value in ev_dict:
				getattr(ev, 'set'+key)(value)


	def __changeMathInEvent(self, tree):
		'''Check the math in trigger and Ea'''
		if not tree: 
			return
		if tree.isName(): 
			self.__changeNameInEvent(tree)
		self.__changeMathInEvent(tree.getLeftChild())
		self.__changeMathInEvent(tree.getRightChild())
	
	def __changeNameInEvent(self, tree):
		'''Change name if neccessary'''
		if tree.getName() in self.alter_dict['Pa']:
			tree.setName(self.alter_dict['Pa'][tree.getName()]['Id'])
			return
		if tree.getName() in self.alter_dict['Sp'].keys():
			tree.setName(self.alter_dict['Sp'][tree.getName()]['Id'])
			return



